#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <umm_malloc/umm_malloc.h>
#include <umm_malloc/umm_heap_select.h>
#include <string>
#include <ArduinoZlib.h>
#include <ArduinoJson.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define I2C_SDA 0
#define I2C_SCL 2
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES 10

#define LOGO_HEIGHT 16
#define LOGO_WIDTH 16
static const unsigned char PROGMEM logo_bmp[] = {0b00000000, 0b11000000,
                                                 0b00000001, 0b11000000,
                                                 0b00000001, 0b11000000,
                                                 0b00000011, 0b11100000,
                                                 0b11110011, 0b11100000,
                                                 0b11111110, 0b11111000,
                                                 0b01111110, 0b11111111,
                                                 0b00110011, 0b10011111,
                                                 0b00011111, 0b11111100,
                                                 0b00001101, 0b01110000,
                                                 0b00011011, 0b10100000,
                                                 0b00111111, 0b11100000,
                                                 0b00111111, 0b11110000,
                                                 0b01111100, 0b11110000,
                                                 0b01110000, 0b01110000,
                                                 0b00000000, 0b00110000};

const char *ssid = "iKuai2G-cc7b";
const char *password = "314314314";

int count = 0;
unsigned long lastStatsTime = 0;
#define XPOS 0
#define YPOS 1
#define DELTAY 2
static int8_t f, icons[NUMFLAKES][3];

void testanimate()
{
  // Initialize 'snowflake' positions
  for (f = 0; f < NUMFLAKES; f++)
  {
    icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS] = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }
}

void stats(const char *what)
{
  uint32_t free;
  uint32_t max;
  uint8_t frag;
  ESP.getHeapStats(&free, &max, &frag);

  Serial.printf("free: %7u - max: %7u - frag: %3d%% <- ", free, max, frag);
  Serial.println(what);
}

void gzip_uncompress(int size, uint8_t *inbuff) {
    int len = 3000;  // 估计最大 JSON 3KB,解压占用内存大约10倍
    uint8_t *outbuf = (uint8_t*)malloc(len); 
    uint32_t outprintsize = 0;

    int result = ArduinoZlib::libmpq__decompress_zlib(inbuff, size, outbuf, len, outprintsize);

    if (result > 0) {
      Serial.println("✅ GZIP success");
      String json = String((char*)outbuf).substring(0, result);
      Serial.println("Response: " + json);
    } else {
      Serial.println("❌ GZIP failed");
      return;
    }

    StaticJsonDocument<1024> doc;
    DeserializationError err = deserializeJson(doc, outbuf);

    if (!err) {
      const char* temp = doc["now"]["temp"];
      const char* humidity = doc["now"]["humidity"];
      Serial.println("🌡: " + String(temp) + "°C");
      Serial.println("💧 : " + String(humidity));
    } else {
      Serial.printf("❌ JSON Failed: %s\n", err.c_str());
    }

    free(outbuf);
}

void http_get()
{
  WiFiClientSecure client;
  HTTPClient https;
  client.setInsecure();

  delay(4000);

  Serial.print("[HTTPS] begin...\n");
  if (https.begin(client, "https://mt5xmd4ue5.re.qweatherapi.com/v7/weather/now?location=101280601&key=9e58b00b22874c5690efccd5e83c7d9b"))
  { // HTTP

    Serial.print("[HTTPS] GET...\n");
    int httpCode = https.GET();

    if (httpCode > 0)
    {
      for (int i = 0; i < https.headers(); i++)
      {
        Serial.printf("%s: %s\n", https.headerName(i).c_str(), https.header(i).c_str());
      }

      Serial.printf("[HTTPS] GET... code: %d\n", httpCode);

      WiFiClient *stream = &https.getStream();
      int size = https.getSize();
      uint8_t *inbuff = (uint8_t*)malloc(size);

      if (!inbuff) {
        Serial.println("❌ 内存不足");
        return;
      }
      
      stream->readBytes(inbuff, size);
      // Serial.printf("头部字节: %02X %02X %02X\n", inbuff[0], inbuff[1], inbuff[2]);
      gzip_uncompress(size, inbuff);
    }
    else
    {
      Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
    }

    https.end();
  }
  else
  {
    Serial.println("[HTTP] Unable to connect");
  }
}

void connect_WIFI()
{
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);

  Serial.begin(115200);

  Serial.println();
  Serial.println();
  Serial.println();

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print("wait\n");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void draw_display()
{
  Wire.begin(I2C_SDA, I2C_SCL);

  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    Serial.println("SSD1306 allocation failed");
    for (;;)
      ;
  }

  display.display();
  delay(2000);

  display.clearDisplay();

  Serial.println("OLED 初始化完成");


  display.drawPixel(10, 10, SSD1306_WHITE);

  display.display();
  delay(2000);

  testdrawline(); // Draw many lines

  testdrawrect(); // Draw rectangles (outlines)

  testfillrect(); // Draw rectangles (filled)

  testdrawcircle(); // Draw circles (outlines)

  testfillcircle(); // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle(); // Draw triangles (outlines)

  testfilltriangle(); // Draw triangles (filled)

  testdrawchar(); // Draw characters of the default font

  testdrawstyles(); // Draw 'stylized' characters

  testscrolltext(); // Draw scrolling text

  testdrawbitmap(); // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(); // Animate bitmaps
}

void setup()
{
  connect_WIFI();
  http_get();
  draw_display();
}

void loop()
{
  unsigned long now = millis();

  if (now - lastStatsTime >= 5000)
  {
    lastStatsTime = now;
    stats(std::to_string(count++).c_str());
  }

  display.clearDisplay();

  // Draw each snowflake:
  for (f = 0; f < NUMFLAKES; f++)
  {
    display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
  }

  display.display(); // Show the display buffer on the screen
  delay(100);

  // Then update coordinates of each flake...
  for (f = 0; f < NUMFLAKES; f++)
  {
    icons[f][YPOS] += icons[f][DELTAY];
    // If snowflake is off the bottom of the screen...
    if (icons[f][YPOS] >= display.height())
    {
      // Reinitialize to a random position, just off the top
      icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
      icons[f][YPOS] = -LOGO_HEIGHT;
      icons[f][DELTAY] = random(1, 6);
    }
  }
}

void testdrawline()
{
  int16_t i;

  display.clearDisplay();

  for (i = 0; i < display.width(); i += 4)
  {
    display.drawLine(0, 0, i, display.height() - 1, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for (i = 0; i < display.height(); i += 4)
  {
    display.drawLine(0, 0, display.width() - 1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for (i = 0; i < display.width(); i += 4)
  {
    display.drawLine(0, display.height() - 1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for (i = display.height() - 1; i >= 0; i -= 4)
  {
    display.drawLine(0, display.height() - 1, display.width() - 1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for (i = display.width() - 1; i >= 0; i -= 4)
  {
    display.drawLine(display.width() - 1, display.height() - 1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for (i = display.height() - 1; i >= 0; i -= 4)
  {
    display.drawLine(display.width() - 1, display.height() - 1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for (i = 0; i < display.height(); i += 4)
  {
    display.drawLine(display.width() - 1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for (i = 0; i < display.width(); i += 4)
  {
    display.drawLine(display.width() - 1, 0, i, display.height() - 1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2; i += 2)
  {
    display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2; i += 3)
  {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width() - i * 2, display.height() - i * 2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < max(display.width(), display.height()) / 2; i += 2)
  {
    display.drawCircle(display.width() / 2, display.height() / 2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void)
{
  display.clearDisplay();

  for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 3)
  {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2 - 2; i += 2)
  {
    display.drawRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i,
                          display.height() / 4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2 - 2; i += 2)
  {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i,
                          display.height() / 4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void)
{
  display.clearDisplay();

  for (int16_t i = 0; i < max(display.width(), display.height()) / 2; i += 5)
  {
    display.drawTriangle(
        display.width() / 2, display.height() / 2 - i,
        display.width() / 2 - i, display.height() / 2 + i,
        display.width() / 2 + i, display.height() / 2 + i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void)
{
  display.clearDisplay();

  for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 5)
  {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
        display.width() / 2, display.height() / 2 - i,
        display.width() / 2 - i, display.height() / 2 + i,
        display.width() / 2 + i, display.height() / 2 + i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void)
{
  display.clearDisplay();

  display.setTextSize(1);              // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);             // Start at top-left corner
  display.cp437(true);                 // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for (int16_t i = 0; i < 256; i++)
  {
    if (i == '\n')
      display.write(' ');
    else
      display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void)
{
  display.clearDisplay();

  display.setTextSize(1);              // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x"));
  display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void)
{
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display(); // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void)
{
  display.clearDisplay();

  display.drawBitmap(
      (display.width() - LOGO_WIDTH) / 2,
      (display.height() - LOGO_HEIGHT) / 2,
      logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}
